package com.zhang.spring.boot.mongo;

import com.zhang.data.mongo.core.MongoUtil;
import org.bson.BsonDocument;
import org.bson.Document;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class QueryMethodInvoker {

    private static final String QUERY_PATTERN ="find|read|get|steam";
    private static final Pattern PREFIX_TEMPLATE = Pattern.compile("^("+QUERY_PATTERN+")((\\p{Lu}.*?))??By");

    private Method method;

    private Class domainClazz;

    private final MongoRepository mongoRepository;

    private final MongoUtil mongoUtil;

    private QueryType queryType;

    private Map<String,Stub> stub = new HashMap<>();

    private Document document;

    public QueryMethodInvoker(Method method, MongoRepository mongoRepository, MongoUtil mongoUtil, Class<?> domainClazz) {
        this.mongoUtil = mongoUtil;
        this.mongoRepository = mongoRepository;
        this.domainClazz=domainClazz;
        try {
            this.method = mongoRepository.getClass().getMethod(method.getName(), method.getParameterTypes());
            queryType = QueryType.BASE;
        } catch (NoSuchMethodException e) {
            this.method=method;
            if (method.isAnnotationPresent(NativeQuery.class)) {
                queryType = QueryType.NATIVE;
                //TODO
                NativeQuery query = method.getDeclaredAnnotation(NativeQuery.class);
                System.out.println(query.value());
                document =Document.parse(query.value());
                convertTemplate(document,this.stub);
                System.out.println(document);
            }else {
                queryType = QueryType.METHODGE;
                String source = method.getName();
                Matcher matcher = PREFIX_TEMPLATE.matcher(source);

                if(matcher.find()){
                    PartTree tree  = new PartTree(source.substring(matcher.group().length()),domainClazz);
                    StringBuilder sb = new StringBuilder(tree.getDocument().toJson());
                    int index=sb.indexOf("?");
                    int num =1;
                    while (index >0){
                        String value="?"+num++;
                        sb.replace(index,index+1,value);
                        index = sb.indexOf("?",index+2);
                    }
                    document =Document.parse(sb.toString());
                    convertTemplate(document,this.stub);
                    System.out.println(document);
                }
            }
        }
    }

    public Object invoke(Object[] arguments) throws InvocationTargetException, IllegalAccessException {
        switch (queryType) {
            case BASE:
                return method.invoke(this.mongoRepository, arguments);
            case NATIVE:
                if (stub.size()!=arguments.length){
                    throw new IllegalStateException("the number of arguments and query string are not matched");
                }
                for (int i=0;i<arguments.length;i++){
                    int index =i+1;
                    Stub stub1 =stub.get("?"+index);
                    stub1.getDocument().put(stub1.getKey(),arguments[i]);
                }
                List<?> list=mongoUtil.find(this.document,domainClazz);
                return list;
            case METHODGE:
                if (stub.size()!=arguments.length){
                    throw new IllegalStateException("the number of arguments and query string are not matched");
                }
                for (int i=0;i<arguments.length;i++){
                    int index =i+1;
                    Stub stub1 =stub.get("?"+index);
                    stub1.getDocument().put(stub1.getKey(),arguments[i]);
                }
                return mongoUtil.find(this.document,domainClazz);
            default:
                break;

        }
        return method.invoke(this.mongoRepository, arguments);
    }

    static class Stub{

        private Document document;
        private String key;

        public Stub(Document document, String key) {
            this.document = document;
            this.key = key;
        }


        public Document getDocument() {
            return document;
        }

        public String getKey() {
            return key;
        }

    }


    public static void convertTemplate(Document document, Map<String,Stub> stub){
        for(String key:document.keySet()){
            Object value=document.get(key);
            if (value instanceof String){
                String stubIn = (String)value;
                if (stubIn.startsWith("?")){
                    stub.put(stubIn,new Stub(document,key));
                }

            }
            if (value instanceof List){
                List<Document> documents =(List<Document>) value;
                documents.forEach( doc -> convertTemplate(doc, stub));
            }
            if(value instanceof Document){
                convertTemplate((Document) value,stub);
            }
        }
    }

    public static void main(String[] args) {
        Document document = Document.parse("{x:\"zhang\"}");
        System.out.println(document);
        System.out.println(Document.parse("{$or:[{x:2},{x:-1}]}"));
        System.out.println(new Document().append("x",2));
//        Document template =Document.parse("{x:?}");
        BsonDocument bsonDocument =BsonDocument.parse("{x:\"?1\"}");
        System.out.println(bsonDocument);
        Map<String,Stub> stub = new HashMap<>();
//        convertTemplate(bsonDocument,stub);
        System.out.println(stub);


        BsonDocument b1 =BsonDocument.parse("{$or:[{x:2},{x:-1}]}");
//        b1.put
        System.out.println(b1);
        System.out.println(b1.get("$or"));
        System.out.println(b1.get("$or").asArray().get(0));
    }
}


